﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Novell.Directory.Ldap;
using System.Security.Cryptography.X509Certificates;
using System.Security;

namespace Sio.Mdm.Connector
{
    //http://directoryprogramming.net/forums/post/840.aspx
    public class ConnectorMono : ConnectorBase
    {
        private LdapConnection _ldapConnection;

        #region Constructor
        public ConnectorMono()
        {
                        
        }

        public ConnectorMono(eLdapDirectoryType directoryType, string directoryBase)
        {
            _directoryType = directoryType;
            _directoryBase = directoryBase;
        }
        #endregion

        #region Connect
        public override void Connect(string server, int port, string userName, string password, bool useSSL)        
        {
            _isConnected = false;
            _isUsingSSL = useSSL;
            _server = server;
            _port = port;
            
            if (_ldapConnection == null) _ldapConnection = new LdapConnection();
            else _ldapConnection.Disconnect();

            if (useSSL)
            {
                _ldapConnection.SecureSocketLayer = true;
                _ldapConnection.UserDefinedServerCertValidationDelegate += new CertificateValidationCallback(ServerCallback);
            }
           
            _ldapConnection.Connect(server, port);
            _ldapConnection.Bind(userName, password);
            //_ldapConnection.Bind("CN=Uroš Kos,OU=Uporabniki,OU=Sarma,DC=Sarma,DC=local", password);                                              
            
            _isConnected = true;
        }                
        #endregion
              
        #region Exists
        public override bool Exists(string value, eLdapSearchField searchField, eLdapSearchScope searchScope, string baseDN, eLdapSearchObject searchObject)
        {
            value = GetSafeSearchPhrase(value);     // TODO: this is workaround...

            if (String.IsNullOrEmpty(baseDN)) baseDN = directoryBase;
            int scope = LdapConnection.SCOPE_ONE;
            if (searchScope == eLdapSearchScope.subtree) scope = LdapConnection.SCOPE_SUB;                                  

            string criteria = "";
            if (searchObject == eLdapSearchObject.user) criteria = GetUserSearchCriteria(searchField, value);
            if (searchObject == eLdapSearchObject.group) criteria = GetGroupSearchCriteria(searchField, value);

            //criteria = "(&(objectClass=person)(eduPersonPrincipalName=šŠčČžŽđĐ))";

            //UTF7Encoding u7 = new UTF7Encoding();
            //UTF8Encoding u8 = new UTF8Encoding();
            //string temp = ConvertToHex("šŠčČžŽđĐ");
            //byte[] b0 = Encoding.BigEndianUnicode.GetBytes("š");
            //byte[] b = u7.GetBytes("š");
            //byte[] b2 = u8.GetBytes("š");
            
            LdapSearchResults results = _ldapConnection.Search(baseDN, scope, criteria, null, false);   
            
            if (!results.hasMore())     // we can not check 'count' here !?          
                return false;
            else
            {
                LdapEntry entry = results.next();
                _lastDN = entry.DN;
            }                
            
            return true;                        
        }
        #endregion        

        public string ConvertToHex(string asciiString)
        {
            string hex = "";
            foreach (char c in asciiString)
            {
                int tmp = c;
                hex += String.Format("{0:x2}", (uint)System.Convert.ToUInt32(tmp.ToString()));
            }
            return hex;
        }

        #region Load
        public ldapObject Load(string DN)
        {            
            ldapObject ldapObj = new ldapObject(DN);
            string baseDN = GetBaseDN(DN);
            string baseCN = GetBaseCN(DN);
            
            baseCN = GetSafeSearchPhrase(baseCN);     // TODO: this is workaround...

            int scope = LdapConnection.SCOPE_ONE;
            string criteria = GetUserSearchCriteria(eLdapSearchField.CN, baseCN);
           
            LdapSearchResults results = _ldapConnection.Search(baseDN, scope, criteria, null, false);

            if (results.hasMore())
            {
                LdapEntry entry = results.next();

                LdapAttributeSet attributeSet = entry.getAttributeSet();
                System.Collections.IEnumerator ienum = attributeSet.GetEnumerator();
                while (ienum.MoveNext())
                { 
                    LdapAttribute attribute = (LdapAttribute)ienum.Current;
                    string _name = attribute.Name;
                    foreach (string _value in attribute.StringValueArray)
                    {
                        ldapObj.Add(_name, _value);
                    }
                }
            }
            else
            {
                throw new Exception(String.Format("ldapObject with DN={0} not found", DN));
            }

            return ldapObj;
        }

        public List<ldapObject> LoadGroups(string baseDN) 
        {            
            List<ldapObject> list = new List<ldapObject>();

            int scope = LdapConnection.SCOPE_ONE;
            string criteria = GetGroupSearchCriteria(eLdapSearchField.All, "");

            LdapSearchResults results = _ldapConnection.Search(baseDN, scope, criteria, null, false);

            while (results.hasMore())
            {
                LdapEntry entry = results.next();
                ldapObject ldapObj = new ldapObject(entry.DN);

                LdapAttributeSet attributeSet = entry.getAttributeSet();
                System.Collections.IEnumerator ienum = attributeSet.GetEnumerator();
                while (ienum.MoveNext())
                {
                    LdapAttribute attribute = (LdapAttribute)ienum.Current;
                    string _name = attribute.Name;
                    foreach (string _value in attribute.StringValueArray)
                    {
                        ldapObj.Add(_name, _value);
                    }
                }
                list.Add(ldapObj);
            }
                          
            //if (!Exists(baseDN, eLdapSearchField.DN, eLdapSearchScope.subtree, "", eLdapSearchObject.group))
            //{
            //    throw new Exception(String.Format("Object with DN={0} not found.", baseDN));
            //}
                                                
            return list;
        }
        #endregion

        #region Add Update Delete (Atributess)
        public void Add(ldapObject data)
        {
            PrepareObjectData(data);

            LdapAttributeSet attributes = FillAtributes(data);

            LdapEntry newEntry = new LdapEntry(data.DN, attributes);
            _ldapConnection.Add(newEntry);            
        }
        
        public void Update(ldapObject data)
        {
            PrepareObjectData(data);

            ArrayList modList = new ArrayList();

            foreach (KeyValuePair<string, List<object>> kvp in data)
            {
                string attributeName = kvp.Key;
                List<object> attributeList = (List<object>)kvp.Value;                

                if (attributeList[0].GetType().ToString() == "System.String")
                {
                    LdapAttribute attribute = new LdapAttribute(attributeName, attributeList[0].ToString());
                    modList.Add(new LdapModification(LdapModification.REPLACE, attribute));                    
                }
                else if (attributeList[0].GetType().ToString() == "System.Byte[]")
                {
                    sbyte[] sByte = fromByte((byte[])attributeList[0]);
                    LdapAttribute attribute = new LdapAttribute(attributeName, sByte);
                    modList.Add(new LdapModification(LdapModification.REPLACE, attribute));                    
                }   
            }

            LdapModification[] mods = new LdapModification[modList.Count];
            mods = (LdapModification[])modList.ToArray(typeof(LdapModification));

            _ldapConnection.Modify(data.DN, mods);
        }

        public void Delete(string dn)
        {
            _ldapConnection.Delete(dn);
        }
        #endregion

        #region Add / Remove from group
        public void AddToGroup(string groupDN, string userDN)
        {
            ArrayList modList = new ArrayList();

            LdapAttribute attribute = new LdapAttribute("member", userDN);
            modList.Add(new LdapModification(LdapModification.ADD, attribute));

            LdapModification[] mods = new LdapModification[modList.Count];
            mods = (LdapModification[])modList.ToArray(typeof(LdapModification));

            _ldapConnection.Modify(groupDN, mods);            
        }

        public void RemoveFromGroup(string groupDN, string userDN)
        {
            ArrayList modList = new ArrayList();

            LdapAttribute attribute = new LdapAttribute("member", userDN);
            modList.Add(new LdapModification(LdapModification.DELETE, attribute));

            LdapModification[] mods = new LdapModification[modList.Count];
            mods = (LdapModification[])modList.ToArray(typeof(LdapModification));

            _ldapConnection.Modify(groupDN, mods);  
        }

        #endregion

        #region GetDiff
        /// <summary>
        /// Each key in data1 is compared to data2. Equal keys are deleted! 
        /// </summary>
        /// <param name="data1"></param>
        /// <param name="data2"></param>
        /// <returns></returns>
        public ldapObject GetDiff(ldapObject data1, ldapObject data2)
        {
            List<string> keys = data1.GetKeys();

            foreach (string key in keys)
            {
                List<object> values1 = data1[key];
                if (data2.Contains(key))
                { 
                    // COMPARE
                    List<object> values2 = data2[key];
                    if (values1[0].ToString() == values2[0].ToString())           // TODO: add support for multiple values in same key
                    {
                        data1.Remove(key);
                    }
                }
            }
            
            return data1;
        }
        #endregion

        #region RenameCN
        public void RenameCN(string currentDN, string newCN)
        {
            if (!newCN.ToUpper().Contains("CN=")) newCN = String.Format("CN={0}", newCN);
            _ldapConnection.Rename(currentDN, newCN, true);
        }
        #endregion
               
        #region Private
        private LdapAttributeSet FillAtributes(ldapObject data)
        {
            LdapAttributeSet attributesSet = new LdapAttributeSet();
            attributesSet.Add(new LdapAttribute("objectclass",data.objectClass));
            attributesSet.Add(new LdapAttribute("cn", data.CN));
                        
            foreach (KeyValuePair<string, List<object>> kvp in data)
            {
                string attributeName = kvp.Key;
                List<object> attributeList = (List<object>)kvp.Value;

                //attributesSet.Add(new LdapAttribute(attributeName, attributeList.ToArray()));

                if (attributeList[0].GetType().ToString() == "System.String")
                {
                     attributesSet.Add(new LdapAttribute(attributeName, attributeList[0].ToString()));
                }
                else if (attributeList[0].GetType().ToString() == "System.Byte[]")
                {
                    //sbyte[] sByte = (sbyte[])(Array)attributeList[0];   
                    //attributesSet.Add(new LdapAttribute(attributeName, sByte));
                    sbyte[] sByte = fromByte((byte[])attributeList[0]);
                    attributesSet.Add(new LdapAttribute(attributeName, sByte));
                }                
            }

            return attributesSet;
        }

        private sbyte[] fromByte(byte[] bytes)
        {
            sbyte[] sByte = new sbyte[bytes.Length];
            for (int i = 0; i < sByte.Length; i++)
            {
                sbyte s = (sbyte)bytes[i];
                sByte[i] = s;
            }
            return sByte;
        }

        // Precej GRD workaround za iskanje (problematični šumniki)
        private string GetSafeSearchPhrase(string searchPhrase)
        {
            searchPhrase = searchPhrase.Replace("š","*");
            searchPhrase = searchPhrase.Replace("Š","*");       
            searchPhrase = searchPhrase.Replace("č","*");       
            searchPhrase = searchPhrase.Replace("Č","*");       
            searchPhrase = searchPhrase.Replace("ž","*");
            searchPhrase = searchPhrase.Replace("Ž", "*");
            searchPhrase = searchPhrase.Replace("đ", "*");
            searchPhrase = searchPhrase.Replace("Đ", "*");
            
            searchPhrase = searchPhrase.Replace("**","*");
       
            return searchPhrase;
        }

        private static bool ServerCallback(X509Certificate certificate, int[] args)
        {
            return true;
        }

        #endregion

    }
}
